package de.zainodis.commons.component.ui.keyboard;

import android.inputmethodservice.InputMethodService;
import android.inputmethodservice.Keyboard;
import android.inputmethodservice.KeyboardView;
import android.text.method.MetaKeyKeyListener;
import android.view.KeyCharacterMap;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputConnection;
import de.zainodis.commons.R;

/*
 * TODO the way keyboards currently work, this is useless, all i wanted was to ease entering of currency values... 
 * not set this keyboard as the new standard keyboard for the whole phone *grrr*...
 */
public class CurrencyKeyboard extends InputMethodService implements
	 KeyboardView.OnKeyboardActionListener {
   static final boolean DEBUG = false;

   /**
    * This boolean indicates the optional example code for performing processing
    * of hard keys in addition to regular text generation from on-screen
    * interaction. It would be used for input methods that perform language
    * translations (such as converting text entered on a QWERTY keyboard to
    * Chinese), but may not be used for input methods that are primarily
    * intended to be used for on-screen text entry.
    */
   static final boolean PROCESS_HARD_KEYS = true;

   private KeyboardView inputView;

   private StringBuilder composing = new StringBuilder();
   private int lastDisplayWidth;
   private long metaState;

   private LatinKeyboard keyboard;

   /**
    * This is the point where you can do all of your UI initialisation. It is
    * called after creation and any configuration change.
    */
   @Override
   public void onInitializeInterface() {
	 if (keyboard != null) {
	    // Configuration changes can happen after the keyboard gets recreated,
	    // so we need to be able to re-build the keyboards if the available
	    // space has changed.
	    int displayWidth = getMaxWidth();
	    if (displayWidth == lastDisplayWidth)
		  return;
	    lastDisplayWidth = displayWidth;
	 }
	 keyboard = new LatinKeyboard(this, R.xml.currency_keyboard);
   }

   /**
    * Called by the framework when your view for creating input needs to be
    * generated. This will be called the first time your input method is
    * displayed, and every time it needs to be re-created such as due to a
    * configuration change.
    */
   @Override
   public View onCreateInputView() {
	 inputView = (KeyboardView) getLayoutInflater().inflate(R.layout.input, null);
	 inputView.setOnKeyboardActionListener(this);
	 inputView.setKeyboard(keyboard);
	 return inputView;
   }

   /**
    * This is the main point where we do our initialisation of the input method
    * to begin operating on an application. At this point we have been bound to
    * the client, and are now receiving all of the detailed information about
    * the target of our edits.
    */
   @Override
   public void onStartInput(EditorInfo attribute, boolean restarting) {
	 super.onStartInput(attribute, restarting);

	 // Reset our state. We want to do this even if restarting, because
	 // the underlying state of the text editor could have changed in any way.
	 composing.setLength(0);

	 if (!restarting) {
	    // Clear shift states.
	    metaState = 0;
	 }
   }

   /**
    * This is called when the user is done editing a field. We can use this to
    * reset our state.
    */
   @Override
   public void onFinishInput() {
	 super.onFinishInput();

	 // Clear current composing text and candidates.
	 composing.setLength(0);

	 // We only hide the candidates window when finishing input on
	 // a particular editor, to avoid popping the underlying application
	 // up and down if the user is entering text into the bottom of
	 // its window.
	 setCandidatesViewShown(false);
	 if (inputView != null) {
	    inputView.closing();
	 }
   }

   @Override
   public void onStartInputView(EditorInfo attribute, boolean restarting) {
	 super.onStartInputView(attribute, restarting);
	 // Apply the selected keyboard to the input view.
	 inputView.setKeyboard(keyboard);
	 inputView.closing();
   }

   /**
    * Deal with the editor reporting movement of its cursor.
    */
   @Override
   public void onUpdateSelection(int oldSelStart, int oldSelEnd, int newSelStart, int newSelEnd,
	    int candidatesStart, int candidatesEnd) {
	 super.onUpdateSelection(oldSelStart, oldSelEnd, newSelStart, newSelEnd, candidatesStart,
		  candidatesEnd);

	 // If the current selection in the text view changes, we should
	 // clear whatever candidate text we have.
	 if (composing.length() > 0 && (newSelStart != candidatesEnd || newSelEnd != candidatesEnd)) {
	    composing.setLength(0);
	    InputConnection ic = getCurrentInputConnection();
	    if (ic != null) {
		  ic.finishComposingText();
	    }
	 }
   }

   /**
    * This translates incoming hard key events in to edit operations on an
    * InputConnection. It is only needed when using the PROCESS_HARD_KEYS
    * option.
    */
   private boolean translateKeyDown(int keyCode, KeyEvent event) {
	 metaState = MetaKeyKeyListener.handleKeyDown(metaState, keyCode, event);
	 int c = event.getUnicodeChar(MetaKeyKeyListener.getMetaState(metaState));
	 metaState = MetaKeyKeyListener.adjustMetaAfterKeypress(metaState);
	 InputConnection ic = getCurrentInputConnection();
	 if (c == 0 || ic == null) {
	    return false;
	 }

	 if ((c & KeyCharacterMap.COMBINING_ACCENT) != 0) {
	    c = c & KeyCharacterMap.COMBINING_ACCENT_MASK;
	 }

	 if (composing.length() > 0) {
	    char accent = composing.charAt(composing.length() - 1);
	    int composed = KeyEvent.getDeadChar(accent, c);

	    if (composed != 0) {
		  c = composed;
		  composing.setLength(composing.length() - 1);
	    }
	 }

	 onKey(c, null);

	 return true;
   }

   /**
    * Use this to monitor key events being delivered to the application. We get
    * first crack at them, and can either resume them or let them continue to
    * the app.
    */
   @Override
   public boolean onKeyDown(int keyCode, KeyEvent event) {
	 switch (keyCode) {
	 case KeyEvent.KEYCODE_BACK:
	    // The InputMethodService already takes care of the back
	    // key for us, to dismiss the input method if it is shown.
	    // However, our keyboard could be showing a pop-up window
	    // that back should dismiss, so we first allow it to do that.
	    if (event.getRepeatCount() == 0 && inputView != null) {
		  if (inputView.handleBack()) {
			return true;
		  }
	    }
	    break;

	 case KeyEvent.KEYCODE_DEL:
	    // Special handling of the delete key: if we currently are
	    // composing text for the user, we want to modify that instead
	    // of let the application to the delete itself.
	    if (composing.length() > 0) {
		  onKey(Keyboard.KEYCODE_DELETE, null);
		  return true;
	    }
	    break;

	 case KeyEvent.KEYCODE_ENTER:
	    // Let the underlying text editor always handle these.
	    return false;

	 default:
	    // For all other keys, if we want to do transformations on
	    // text being entered with a hard keyboard, we need to process
	    // it and do the appropriate action.
	    if (PROCESS_HARD_KEYS) {
		  if (keyCode == KeyEvent.KEYCODE_SPACE
			   && (event.getMetaState() & KeyEvent.META_ALT_ON) != 0) {
			// A silly example: in our input method, Alt+Space
			// is a shortcut for 'android' in lower case.
			InputConnection ic = getCurrentInputConnection();
			if (ic != null) {
			   // First, tell the editor that it is no longer in the
			   // shift state, since we are consuming this.
			   ic.clearMetaKeyStates(KeyEvent.META_ALT_ON);
			   keyDownUp(KeyEvent.KEYCODE_A);
			   keyDownUp(KeyEvent.KEYCODE_N);
			   keyDownUp(KeyEvent.KEYCODE_D);
			   keyDownUp(KeyEvent.KEYCODE_R);
			   keyDownUp(KeyEvent.KEYCODE_O);
			   keyDownUp(KeyEvent.KEYCODE_I);
			   keyDownUp(KeyEvent.KEYCODE_D);
			   // And we consume this event.
			   return true;
			}
		  }
		  if (translateKeyDown(keyCode, event)) {
			return true;
		  }
	    }
	 }

	 return super.onKeyDown(keyCode, event);
   }

   /**
    * Use this to monitor key events being delivered to the application. We get
    * first crack at them, and can either resume them or let them continue to
    * the app.
    */
   @Override
   public boolean onKeyUp(int keyCode, KeyEvent event) {
	 // If we want to do transformations on text being entered with a hard
	 // keyboard, we need to process the up events to update the meta key
	 // state we are tracking.
	 return super.onKeyUp(keyCode, event);
   }

   /**
    * Helper function to commit any text being composed in to the editor.
    */
   private void commitTyped(InputConnection inputConnection) {
	 if (composing.length() > 0) {
	    inputConnection.commitText(composing, composing.length());
	    composing.setLength(0);
	 }
   }

   /**
    * Helper to send a key down / key up pair to the current editor.
    */
   private void keyDownUp(int keyEventCode) {
	 getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_DOWN, keyEventCode));
	 getCurrentInputConnection().sendKeyEvent(new KeyEvent(KeyEvent.ACTION_UP, keyEventCode));
   }

   // Implementation of KeyboardViewListener

   public void onKey(int primaryCode, int[] keyCodes) {
	 if (primaryCode == Keyboard.KEYCODE_DELETE) {
	    handleBackspace();
	 } else if (primaryCode == Keyboard.KEYCODE_CANCEL) {
	    handleClose();
	    return;
	 } else {
	    handleCharacter(primaryCode, keyCodes);
	 }
   }

   public void onText(CharSequence text) {
	 InputConnection ic = getCurrentInputConnection();
	 if (ic == null)
	    return;
	 ic.beginBatchEdit();
	 if (composing.length() > 0) {
	    commitTyped(ic);
	 }
	 ic.commitText(text, 0);
	 ic.endBatchEdit();
   }

   private void handleBackspace() {
	 final int length = composing.length();
	 if (length > 1) {
	    composing.delete(length - 1, length);
	    getCurrentInputConnection().setComposingText(composing, 1);
	 } else if (length > 0) {
	    composing.setLength(0);
	    getCurrentInputConnection().commitText("", 0);
	 } else {
	    keyDownUp(KeyEvent.KEYCODE_DEL);
	 }
   }

   private void handleCharacter(int primaryCode, int[] keyCodes) {
	 getCurrentInputConnection().commitText(String.valueOf((char) primaryCode), 1);
   }

   private void handleClose() {
	 commitTyped(getCurrentInputConnection());
	 requestHideSelf(0);
	 inputView.closing();
   }

   @Override
   public void swipeLeft() {
	 handleBackspace();
   }

   @Override
   public void swipeRight() {

   }

   @Override
   public void swipeDown() {
	 handleClose();
   }

   @Override
   public void swipeUp() {
   }

   @Override
   public void onPress(int primaryCode) {
   }

   @Override
   public void onRelease(int primaryCode) {
   }

}
